package com.tanlu.tcc.interceptor;

import com.tanlu.tcc.TransactionContext;
import com.tanlu.tcc.annotation.Propagation;
import com.tanlu.tcc.annotation.TransactionCC;
import com.tanlu.tcc.domain.InvocationContext;
import com.tanlu.tcc.domain.Participant;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicBoolean;


@Aspect
@Component
public class ResourceCoordinatorAspect {

    private static AtomicBoolean isCancel = new AtomicBoolean(true);

    @Autowired
    private TransactionContext transactionContext;

    @Pointcut("@annotation(com.tanlu.tcc.annotation.TransactionCC)")
    public void transactionContextCall() {

    }

    @Around("transactionContextCall()")
    public Object interceptTransactionContextMethod(ProceedingJoinPoint pjp) {

        Method method = this.getTransactionMethod(pjp);
        TransactionCC transactionCC = method.getAnnotation(TransactionCC.class);
        String serviceName = transactionCC.serviceName();
        String className = transactionCC.className();
        String confirmMethodName = transactionCC.confirmMethod();
        String cancelMethodName = transactionCC.cancelMethod();
        Propagation propagation = transactionCC.propagation();
        Object obj = null;
        System.out.println(className + " " + confirmMethodName + " " + cancelMethodName);
        try {
            int outTime = transactionCC.outTime();
            Participant participant = createParticipant(pjp, serviceName, className, confirmMethodName, cancelMethodName);
            transactionContext.transactionTry(participant, propagation, outTime);
            obj = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            transactionContext.cancel();
            isCancel.set(false);
            return obj;
        }
        if(transactionCC.isRoot() && isCancel.get()) {
            System.out.println(className + " send");
            transactionContext.confirm();
        }

        isCancel.set(true);
        return obj;
    }

    public Participant createParticipant(ProceedingJoinPoint pjp,String serviceName, String className, String confirmMethodName, String cancelMethodName) {
        Method method = this.getTransactionMethod(pjp);

        InvocationContext confirmInvocation = new InvocationContext(className,
                confirmMethodName,
                method.getParameterTypes(), pjp.getArgs());

        InvocationContext cancelInvocation = new InvocationContext(className,
                cancelMethodName,
                method.getParameterTypes(), pjp.getArgs());

        Participant participant =
                new Participant(
                        serviceName,
                        confirmInvocation,
                        cancelInvocation);
        return participant;
    }

    public Method getTransactionMethod(ProceedingJoinPoint pjp) {
        Method method = ((MethodSignature) (pjp.getSignature())).getMethod();

        if (method.getAnnotation(TransactionCC.class) == null) {
            try {
                method = pjp.getTarget().getClass().getMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                return null;
            }
        }
        return method;
    }

    public Class getDeclaringType(String className, String methodName, Class<?>[] parameterTypes) {

        Method method = null;
        Class aClass = null;
        try {
            aClass = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        Class findClass = aClass;
        do {
            Class[] clazzes = findClass.getInterfaces();
            for (Class clazz : clazzes) {
                try {
                    method = clazz.getDeclaredMethod(methodName, parameterTypes);
                } catch (NoSuchMethodException e) {
                    method = null;
                }
                if (method != null) {
                    return clazz;
                }
            }
            findClass = findClass.getSuperclass();
        } while (!findClass.equals(Object.class));
        return aClass;
    }

}
